"""谱聚类算法基于图论，它的起源可以追溯到早期的图分割文献。
不过，直至近年来，受益于计算机计算能力的提升，谱聚类算法才得到了广泛的研究和关注。

谱聚类被广泛应用于图像分割、社交网络分析、推荐系统、文本聚类等领域。
例如，在图像分割中，谱聚类可以有效地将图像划分为背景和前景；
在社交网络分析中，它可以识别出不同的社区结构。

1. 算法概述
谱聚类的基本原理是将数据点视为图中的顶点，根据数据点之间的相似性构建图的边。
它首先计算图的拉普拉斯矩阵的特征向量，然后利用这些特征向量进行聚类。
这种方法能够捕捉到数据的非线性结构，因此在许多应用中表现优异。

所谓拉普拉斯矩阵，是一种用于表示一个图的矩阵形式。
对于给定的一个有n个顶点的图G，它的拉普拉斯矩阵定义为L=D−A。其中D为图的度矩阵，A为图的邻接矩阵。

2. 创建样本数据
为验证谱聚类的效果，用scikit-learn中的样本生成器创建2个非线性结构的数据集。"""
from matplotlib import pyplot as plt
from sklearn.datasets import make_moons, make_circles
from sklearn.cluster import SpectralClustering
import matplot_config

matplot_config.init_config()
fig, axes = plt.subplots(nrows=1, ncols=2)
fig.set_size_inches((8, 4))

X_moon, y_moon = make_moons(noise=0.05, n_samples=1000)
axes[0].scatter(X_moon[:, 0], X_moon[:, 1], marker="o", c=y_moon, s=25, cmap=plt.cm.spring)

X_circle, y_circle = make_circles(noise=0.05, factor=0.5, n_samples=1000)
axes[1].scatter(X_circle[:, 0], X_circle[:, 1], marker="o", c=y_circle, s=25, cmap=plt.cm.winter)

plt.show()
"""image.png
一个交错的月牙形式，一个是同心圆形式，都是很难线性分割的数据集。

3. 模型训练
首先，用默认的参数训练看看效果："""

# 定义
regs = [
    SpectralClustering(n_clusters=2),
    SpectralClustering(n_clusters=2),
]

# 训练模型
regs[0].fit(X_moon, y_moon)
regs[1].fit(X_circle, y_circle)

fig, axes = plt.subplots(nrows=1, ncols=2)
fig.set_size_inches((8, 4))

# 绘制聚类之后的结果
axes[0].scatter(
    X_moon[:, 0], X_moon[:, 1], marker="o", c=regs[0].labels_, s=25, cmap=plt.cm.spring
)

axes[1].scatter(
    X_circle[:, 0], X_circle[:, 1], marker="o", c=regs[1].labels_, s=25, cmap=plt.cm.winter
)

plt.show()
"""image.png
从图中可以看出，聚类的效果不是很好，从颜色上看，与原始数据的类别相比差距较大。

接下来，调整下SpectralClustering模型的affinity参数，
这个参数的作用是定义数据点之间的相似度矩阵的计算方法。
affinity参数的可选值常用的有两个：

nearest_neighbors：通过计算最近邻图来构建亲和矩阵
rbf：使用径向基函数 （RBF） 内核构建亲和矩阵。
默认的值是 rbf，下面我们试试nearest_neighbors方式的聚类效果。
将上面的代码中 regs 的定义部分换成如下代码：
"""
regs = [
    SpectralClustering(n_clusters=2, affinity="nearest_neighbors"),
    SpectralClustering(n_clusters=2, affinity="nearest_neighbors"),
]

regs[0].fit(X_moon, y_moon)
regs[1].fit(X_circle, y_circle)

fig, axes = plt.subplots(nrows=1, ncols=2)
fig.set_size_inches((8, 4))

# 绘制聚类之后的结果
axes[0].scatter(
    X_moon[:, 0], X_moon[:, 1], marker="o", c=regs[0].labels_, s=25, cmap=plt.cm.spring
)

axes[1].scatter(
    X_circle[:, 0], X_circle[:, 1], marker="o", c=regs[1].labels_, s=25, cmap=plt.cm.winter
)

plt.show()
"""image.png
修改参数之后的聚类效果与原始数据就非常接近了。

4. 总结
简而言之，谱聚类是一个在图上进行聚类的方法，它试图找到图的最佳切割，使得同一簇内的边的权重尽可能大，而不同簇之间的边的权重尽可能小。

这种聚类算法的优势有：

可以捕获数据的非线性结构
对噪声和异常值相对鲁棒
不需要明确的形状假设，适用于各种形状的簇
它的局限性有：

计算复杂度相对较高，尤其是对于大规模数据
需要提前确定簇的数量，这在很多实际应用中是一个挑战
对于高维数据，可能存在“维度诅咒”问题，尽管可以通过降维缓解，但增加了计算复杂度"""